package by.pavlyukevich.paymentsystem.dataaccess.impl;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.SingularAttribute;

import org.apache.commons.lang3.Validate;
import org.hibernate.jpa.criteria.OrderImpl;

import by.pavlyukevich.paymentsystem.dataaccess.AbstractDao;
import by.pavlyukevich.paymentsystem.dataaccess.filter.Filter;

public abstract class AbstractDaoImpl<ID, Entity extends Object> implements AbstractDao<ID, Entity> {

	private EntityManager em;
	private final Class<Entity> entityClass;

	protected AbstractDaoImpl(final Class<Entity> entityClass) {
		Validate.notNull(entityClass, "entityClass could not be a null");
		this.entityClass = entityClass;
	}

	@Override
	public Entity getById(ID id) {
		return em.find(getEntityClass(), id);
	}

	@Override
	public Entity insert(final Entity entity) {
		em.persist(entity);
		return entity;
	}

	@Override
	public Entity update(Entity entity) {
		entity = em.merge(entity);
		em.flush();
		return entity;
	}

	@Override
	public void delete(final ID key) {
		em.remove(em.find(getEntityClass(), key));
	}

	@Override
	public void delete(List<ID> ids) {
		em.createQuery(
				String.format("delete from %s e where e.id in (:ids)", entityClass.getSimpleName()))
				.setParameter("ids", ids).executeUpdate();
	}

	@Override
	public void deleteAll() {
		final Query q1 = em.createQuery("delete from " + getEntityClass().getSimpleName());
		q1.executeUpdate();
		em.flush();
	}

	@PersistenceContext
	protected void setEntityManager(final EntityManager em) {
		this.em = em;
	}

	private Class<Entity> getEntityClass() {
		return entityClass;
	}

	public EntityManager getEntityManager() {
		return em;
	}

	@SuppressWarnings("unused")
	protected <H, T> CriteriaQuery<T> createCriteia(Class<T> resultClass, Class<H> rootClass) {
		CriteriaQuery<T> criteria = em.getCriteriaBuilder().createQuery(resultClass);
		Root<H> root = criteria.from(rootClass);
		return criteria;
	}

	protected <T> T getSingle(CriteriaQuery<T> criteria) {
		TypedQuery<T> query = getEntityManager().createQuery(criteria);
		return query.getSingleResult();
	}

	protected <T> List<T> getList(CriteriaQuery<T> criteria) {
		return getList(criteria, 0, 0);
	}

	protected <T> List<T> getList(CriteriaQuery<T> criteria, int startPostion, int maxResult) {
		TypedQuery<T> query = getEntityManager().createQuery(criteria);

		if (startPostion > 0) {
			query.setFirstResult(startPostion);
		}

		if (maxResult > 0) {
			query.setMaxResults(maxResult);
		}
		return query.getResultList();
	}

	protected <T> List<T> getList(CriteriaQuery<T> criteria, Filter<T> paginParams) {
		if (paginParams == null) {
			return getList(criteria);
		}
		return getList(criteria, paginParams.getStartPosition(), paginParams.getMaxResult());
	}

	@SuppressWarnings("unchecked")
	protected <T> Root<T> getRoot(CriteriaQuery<?> criteria, Class<T> entity) {
		for (Root<?> root : criteria.getRoots()) {
			if (root.getJavaType().equals(entity)) {
				return (Root<T>) root;
			}
		}
		throw new RuntimeException("Root class not found " + entity.getCanonicalName());
	}

	protected CriteriaQuery<Entity> getAllCriteria(SingularAttribute<Entity, ?> attr,
			boolean ascending) {
		CriteriaBuilder cBuilder = getEntityManager().getCriteriaBuilder();

		CriteriaQuery<Entity> criteria = cBuilder.createQuery(entityClass);
		Root<Entity> root = criteria.from(entityClass);

		if (attr != null) {
			criteria.select(root);
			criteria.orderBy(new OrderImpl(root.get(attr), ascending));
		}
		return criteria;
	}

	protected CriteriaQuery<Entity> getAllCriteria(Filter<Entity> pagingParams) {
		if (pagingParams == null) {
			return getAllCriteria();
		}
		return getAllCriteria(pagingParams.getSortParam(), pagingParams.getAscending());
	}

	protected CriteriaQuery<Entity> getAllCriteria() {
		return getAllCriteria(null, true);
	}

	protected CriteriaQuery<Long> getCountAllCriteria() {
		CriteriaBuilder cBuilder = getEntityManager().getCriteriaBuilder();
		CriteriaQuery<Long> criteria = cBuilder.createQuery(Long.class);
		Root<Entity> root = criteria.from(entityClass);
		criteria.select(cBuilder.count(root));
		return criteria;

	}

	@Override
	public List<Entity> getList(Filter<Entity> Filter) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public long getCount(Filter<Entity> Filter) {
		// TODO Auto-generated method stub
		return 0;
	}

}
